
#include <EEPROM.h>

#define PIN_BLUE   3
#define PIN_RED    5
#define PIN_GREEN  6
#define PIN_MIC    0

#define DEBUG
// user configurable variables
int RGB_INCREMENT;
int AVERAGE_LEVEL = 0;

int minLevel = 1024, maxLevel = 0;
int rgbOutput = 0;
int threshold;
int secondsInOverflow = 0;
boolean isOverflowing = false;

const int MAX_SHADE = 255;

byte CoarseR[] =   {  0, 0, 0, 0, 92, 155, 183, 209 };
byte CoarseG[] =   {  0, 24, 153, 168, 188, 211, 223, 234 };
byte CoarseB[] =   {  0, 26, 204, 213, 224, 235, 241, 246 };

void setup() {
    // Configure hardware
    pinMode(PIN_RED, OUTPUT);   
    pinMode(PIN_GREEN, OUTPUT);
    pinMode(PIN_BLUE, OUTPUT);
    pinMode(PIN_MIC, INPUT);
    
    // Load settings
    LoadInt(&RGB_INCREMENT, 0, 3);

    Serial.begin(9600);  
    calibrateMicrophone();
}

void calibrateMicrophone() {
  int counter = 0;
    // calibrate for 3 seconds..
    while(counter < 3000) {
      int micLevel = analogRead(PIN_MIC);
      if (micLevel < minLevel) {
         minLevel = micLevel; 
      } else if (micLevel > maxLevel) {
         maxLevel = micLevel; 
      }
      counter++;
      delay(1);
  }
  AVERAGE_LEVEL = (maxLevel + minLevel) / 2;
  threshold = ((AVERAGE_LEVEL + maxLevel) / 2) - AVERAGE_LEVEL;
  #ifdef DEBUG
  Serial.print("minLevel: ");
  Serial.print(minLevel);
  Serial.print("maxLevel: ");
  Serial.println(maxLevel);
  #endif 
  printMenu();
}

void loop() {
   int micLevel = analogRead(PIN_MIC);
   micLevel = abs(micLevel-AVERAGE_LEVEL);
   if (micLevel > threshold) {
     #ifdef DEBUG
     Serial.println("higher");
     #endif
     rgbOutput += RGB_INCREMENT;
   } else {
     #ifdef DEBUG
     Serial.println("lower");
     #endif
     --rgbOutput;
   }
  
   rgbOutput = constrain(rgbOutput, 0, 255);
   
   setColor(rgbOutput);
   checkMenu();
   delay(5);
}


// 0 .. MAX_SHADE
void setColor(int shade) {
  int divisions = MAX_SHADE / sizeof(CoarseR);
  float coarseValue = (float) shade / divisions;
  int maxCoarseIndex = sizeof(CoarseR) - 2;
  // coarseIndex should be maximum the size of the array
  int coarseIndex = constrain((int) coarseValue, 0, maxCoarseIndex);

  float fineLevel = coarseValue - coarseIndex;
  if (fineLevel > 1.0F) {
    fineLevel = 1.0F;
  }
  // if we are overflowing permanently during more than 5 minutes seconds, lower the microphone's threshold
  if (coarseIndex == maxCoarseIndex && fineLevel == 1.0F) {
      isOverflowing = true;
      secondsInOverflow++;
      // lower threshold after 1 minute
      if (secondsInOverflow == 12000) {
          threshold--; 
          Serial.println("Lowering threshold!");
      }
  } else {
      isOverflowing = false; 
      secondsInOverflow = 0;
  }
  
  #ifdef DEBUG
  Serial.print("coarseIndex: ");
  Serial.println(coarseIndex);
  Serial.print("fineLevel: ");
  Serial.println(fineLevel);
  #endif
  // interpolate between the given color values
  int red = interpolateColor(coarseIndex, fineLevel, CoarseR);
  int green = interpolateColor(coarseIndex, fineLevel, CoarseG);
  int blue = interpolateColor(coarseIndex, fineLevel, CoarseB);

  setOutput(red, green, blue);
}

int interpolateColor(int coarseIndex, float fineLevel, byte* colorValues) {
  int delta = colorValues[coarseIndex + 1] - colorValues[coarseIndex];
  int result = colorValues[coarseIndex] + (float) (fineLevel * delta);

 return result;
}

void setOutput(int red, int green, int blue) {
    #ifdef DEBUG
    Serial.print("Red ");
    Serial.print(red);
    Serial.print(" Green ");
    Serial.print(green);
    Serial.print(" Blue ");
    Serial.println(blue);
    #endif
    analogWrite(PIN_RED, red);
    analogWrite(PIN_GREEN, green);
    analogWrite(PIN_BLUE, blue);
}

void printMenu()
{
    Serial.println("\n--- Interactive Logo ---");
    
    Serial.print("C : calibrate microphone; is ");
    Serial.println(AVERAGE_LEVEL);
    
    Serial.print("I# : set RGB increment to #; is ");
    Serial.println(RGB_INCREMENT);

    Serial.println("S : save settings to EEPROM\n");
}


void checkMenu()
{
    if (Serial.available() > 0)
    {
        /* Read first character, the menu option. There's a little trick
         * to force letters into uppercase by clearing bit 5 */
        int option = Serial.read() & ~0x20;
        
        if (option == 'S') {
            SaveSettings();
        } else if (option == 'C') {
            calibrateMicrophone();
        } else if (option == 'I') {
            int result = readInt();
            RGB_INCREMENT = result; 
            printMenu();
        }	

    }
}

int readInt()
{
    int result = 0;
    while (Serial.available() > 0)
    {
        int ch = Serial.read();
        if (ch >= '0' && ch <= '9')
            result = (result * 10) + (ch - '0'); // Add digit
    }
    return result;
}

void LoadInt(int* destination, int address, int defaultValue)
{
    *destination = EEPROM.read(address++) << 8;
    *destination |= EEPROM.read(address);
    if (*destination == (int)(0xFFFF))
        *destination = defaultValue;
}

void SaveSettings()
{
    int a = 0;
    EEPROM.write(a++, highByte(RGB_INCREMENT));
    EEPROM.write(a++, lowByte(RGB_INCREMENT));
    Serial.println("Saved.");
}
